<?php

  abstract class Data implements ArrayAccess, IteratorAggregate, Countable
  {

      protected
              $data = array(),
              $outfile = null;

      /**
       * Gets the data
       * 
       * @return array 
       */
      public function getData()
      {
          return $this->data;
      }

      public function __call($name, $arguments)
      {
          $name = strtolower($name);

          if (strpos($name, 'to') == 0 && $format = substr($name, 2))
          {
              $len = 0;
              $cut = true;
              $open = '>';
              $break = PHP_EOL;

              $this->outfile = new SplFileObject(!isset($arguments[0]) ? 'outfile.' . $format : $arguments[0], 'w+');

              if ($format == 'tnt')
              {
                  $len = $this->getMaxLenTaxa();
                  $cut = false;
                  $open = '';
                  $break = ' ';
              }
              else if ($format == 'nexus')
              {
                  
              }

              foreach ($this as $seq)
              {
                  $this->outfile->fwrite($open . str_pad($seq->taxa, $len) . $break . wordwrap($seq->chars, 70, PHP_EOL, $cut) . PHP_EOL);
              }
          }
      }

      public function getMaxLenTaxa()
      {
          $max_len = 0;
          foreach ($this as $data)
          {
              if (($len = strlen($data->taxa)) > $max_len)
              {
                  $max_len = $len;
              }
          }
          return $max_len;
      }

      /**
       * Gets a array of positions and 
       * 
       * @return array 
       */
      public function getVectorPositions()
      {
          $vector_pos = array();

          foreach ($this as $data)
          {
              $pos = -1;
              while ($state = substr($data->chars, ++$pos, 1))
              {
                  if (!isset($vector_pos[$pos][$state]))
                  {
                      $vector_pos[$pos][$state] = 0;
                  }
                  $vector_pos[$pos][$state]++;
              }
          }
          return $vector_pos;
      }

      /**
       * Gets the positions
       *
       * @param string $type The site type 
       * @return array 
       */
      public function getSiteTypePositions($type = '')
      {
          $sites = array();
          $vector_pos = $this->getVectorPositions();
          $n_taxa = count($this) + 1;

          foreach ($vector_pos as $pos => $states)
          {
              $site_type = 'conserved';
              if (($n_states = count($states)) != 1)
              {
                  $site_type = in_array($n_taxa - $n_states, $states) ? 'singletons' : 'informative';
              }
              $sites[$site_type][] = $pos;
          }
          return isset($sites[$type]) ? $sites[$type] : $sites;
      }

      /**
       *
       * @return array An associative array
       */
      public function getNbSiteType()
      {
          $sites = $this->getSiteTypePositions();
          foreach ($sites as $type => $value)
          {
              $sites[$type] = count($sites[$type]);
          }

          $sites['nb'] = array_sum($sites);

          return $sites;
      }

      /**
       * Gets informative sites
       * @return \Data 
       */
      public function getInformative()
      {
          $info_sites_pos = $this->getSiteTypePositions('informative');

          foreach ($this as $data)
          {
              $info_chars = '';
              foreach ($info_sites_pos as $pos)
              {
                  $info_chars .= substr($data->chars, $pos, 1);
              }
              $data->set('chars', $info_chars);
          }
          return $this;
      }

      /**
       * Check if sequences have the same length
       * 
       * @return boolean    True if sequences have the same length, false otherwise
       */
      public function isAligned()
      {
          $len = $this->getNbChars();
          foreach ($this as $data)
          {
              if ($len != strlen($data->chars))
              {
                  return false;
              }
          }
          return true;
      }

      /**
       * Gets the number of characters
       * 
       * @return int 
       */
      public function getNbChars()
      {
          return strlen($this[0]->chars);
      }

      /**
       *
       * @return \Data 
       */
      public function getUnique()
      {
          foreach ($this as $i => $data)
          {
              foreach ($this as $k => $seq)
              {
                  if ($i != $k && $data->chars == $seq->chars)
                  {
                      unset($this[$i]);
                  }
              }
          }
          return $this;
      }

      public function getClones()
      {
          $temp = array();
          
          $clones = array();

          foreach ($this as $i => $seq)
          {
              if (!in_array($seq->getChars(), $temp))
              {
                  $temp[] = $seq->getChars();
              }

              $key = array_search($seq->getChars(), $temp);

              $clones[$key][] = $seq->getTaxa();
          }

          return $clones;
      }

      /**
       *  
       * 
       * @return /Data 
       */
      public function getComplement()
      {
          $bases = array('A', 'C', 'G', 'T');
          $comp_bases = array_combine($bases, array_reverse($bases));

          foreach ($this as $data)
          {
              $pos = 0;
              $comp = '';
              while ($state = substr($data->chars, $pos++, 1))
              {
                  $comp .= isset($comp_bases[$state]) ? $comp_bases[$state] : $state;
              }
              $data->chars = $comp;
          }
          return $this;
      }

      public function getReverse()
      {
          foreach ($this as $data)
          {
              $data->chars = strrev($data->chars);
          }
          return $this;
      }

      public function getRevComp()
      {
          return $this->getReverse()->getComplement();
      }

      public function count()
      {
          return count($this->data);
      }

      /**
       * 
       *    
       * @return \ArrayIterator 
       */
      public function getIterator()
      {
          return new ArrayIterator($this->data);
      }

      /**
       *
       * @param type $offset
       * @param type $value 
       */
      public function offsetSet($offset, $value)
      {
          if ($offset === null)
          {
              $this->data[] = $value;
          }
          else
          {
              $this->data[$offset] = $value;
          }
      }

      public function offsetExists($offset)
      {
          return isset($this->data[$offset]);
      }

      public function offsetUnset($offset)
      {
          unset($this->data[$offset]);
      }

      public function offsetGet($offset)
      {
          return isset($this->data[$offset]) ? $this->data[$offset] : null;
      }

  }

?>
